package com.nidu.demo.log.aop;

import cn.hutool.core.util.StrUtil;
import com.nidu.demo.common.constants.RequestHeaderConstants;
import com.nidu.demo.log.gateway.OperateLogGateway;
import com.nidu.demo.log.model.OperateLog;
import com.nidu.demo.security.core.LoginUser;
import com.nidu.demo.security.util.SecurityUtils;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 操作日志切面
 *
 * @author auto
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class OperateLogAspect {

    private final OperateLogGateway operateLogGateway;
    private final ExpressionParser parser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperateLogging operateLog) throws Throwable {
        // 如果禁用日志记录，直接执行
        if (!operateLog.enable()) {
            return joinPoint.proceed();
        }

        Object result = null;
        
        try {
            // 执行业务逻辑
            result = joinPoint.proceed();
            
            // 异步记录操作日志
            asyncSaveOperateLog(joinPoint, operateLog, result, true, null);
            
            return result;
        } catch (Exception e) {
            // 异步记录操作日志
            asyncSaveOperateLog(joinPoint, operateLog, null, false, e.getMessage());
            
            throw e;
        }
    }

    /**
     * 异步保存操作日志
     */
    @Async("operateLogExecutor")
    public void asyncSaveOperateLog(ProceedingJoinPoint joinPoint, 
                                   OperateLogging annotation,
                                   Object result, 
                                   boolean success, 
                                   String errorMsg) {
        try {
            OperateLog logEntity = buildOperateLog(joinPoint, annotation, result, success, errorMsg);
            operateLogGateway.create(logEntity);
            log.debug("操作日志保存成功，操作: {}-{}", logEntity.getType(), logEntity.getSubType());
        } catch (Exception e) {
            log.error("操作日志保存失败", e);
        }
    }

    /**
     * 构建操作日志对象
     */
    private OperateLog buildOperateLog(ProceedingJoinPoint joinPoint, 
                                      OperateLogging annotation,
                                      Object result, 
                                      boolean success, 
                                      String errorMsg) {
        OperateLog logEntity = new OperateLog();
        
        // 设置基本信息
        logEntity.setType(annotation.type())
                .setSubType(annotation.subType())
                .setSuccess(success)
                .setDeleted(false);
        
        // 设置链路追踪ID
        String traceId = MDC.get("traceId");
        if (StrUtil.isNotBlank(traceId)) {
            logEntity.setTraceId(traceId);
        }


        // 设置用户信息
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (loginUser != null) {
                logEntity.setUserId(loginUser.getId())
                        .setUserType(loginUser.getUserType());
            }
        } catch (Exception e) {
            log.debug("获取登录用户信息失败", e);
        }
        
        // 设置请求信息
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                
                logEntity.setRequestMethod(request.getMethod())
                        .setRequestUrl(request.getRequestURI())
                        .setUserIp(ServletUtil.getClientIP(request))
                        .setUserAgent(request.getHeader(RequestHeaderConstants.USER_AGENT));
            }
        } catch (Exception e) {
            log.debug("获取请求信息失败", e);
        }
        
        // 使用SpEL表达式解析动态字段
        EvaluationContext context = createEvaluationContext(joinPoint, result, success, errorMsg);
        
        // 解析操作内容
        if (StrUtil.isNotBlank(annotation.action())) {
            try {
                String action = parseExpression(annotation.action(), context);
                logEntity.setAction(action);
            } catch (Exception e) {
                log.warn("解析操作内容表达式失败: {}", annotation.action(), e);
                logEntity.setAction(annotation.action());
            }
        }
        
        // 解析业务ID
        if (StrUtil.isNotBlank(annotation.bizId())) {
            try {
                String bizIdStr = parseExpression(annotation.bizId(), context);
                if (StrUtil.isNotBlank(bizIdStr) && StrUtil.isNumeric(bizIdStr)) {
                    logEntity.setBizId(Long.parseLong(bizIdStr));
                }
            } catch (Exception e) {
                log.warn("解析业务ID表达式失败: {}", annotation.bizId(), e);
            }
        }
        
        // 解析扩展字段
        if (StrUtil.isNotBlank(annotation.extra())) {
            try {
                String extra = parseExpression(annotation.extra(), context);
                logEntity.setExtra(extra);
            } catch (Exception e) {
                log.warn("解析扩展字段表达式失败: {}", annotation.extra(), e);
                logEntity.setExtra(annotation.extra());
            }
        }
        
        return logEntity;
    }

    /**
     * 创建SpEL表达式上下文
     */
    private EvaluationContext createEvaluationContext(ProceedingJoinPoint joinPoint, 
                                                     Object result, 
                                                     boolean success, 
                                                     String errorMsg) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // 设置方法参数
        Object[] args = joinPoint.getArgs();
        Method method = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod();
        String[] paramNames = discoverer.getParameterNames(method);
        
        if (paramNames != null && args != null) {
            for (int i = 0; i < paramNames.length && i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
        }
        
        // 设置返回结果
        context.setVariable("result", result);
        context.setVariable("success", success);
        context.setVariable("errorMsg", errorMsg);
        
        // 设置用户信息
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (loginUser != null) {
                context.setVariable("currentUser", loginUser);
                context.setVariable("currentUserId", loginUser.getId());
                // LoginUser没有username字段，使用ID代替
                context.setVariable("currentUserId", loginUser.getId());
            }
        } catch (Exception e) {
            log.debug("设置用户上下文失败", e);
        }
        
        return context;
    }

    /**
     * 解析SpEL表达式
     */
    private String parseExpression(String expressionString, EvaluationContext context) {
        if (StrUtil.isBlank(expressionString)) {
            return "";
        }
        
        try {
            Expression expression = parser.parseExpression(expressionString);
            Object value = expression.getValue(context);
            return value != null ? value.toString() : "";
        } catch (Exception e) {
            log.warn("SpEL表达式解析失败: {}", expressionString, e);
            return expressionString;
        }
    }
}
